package com.example.demo.mqserver.core;

/**
 * Created with IntelliJ IDEA.
 * Description
 * User: 杰
 * Date: 2023 -08 -02
 * Time: 14:33
 */

import com.example.demo.common.MqException;

/**
 * 使用该类来实现交换机的转发规则
 * 同时也借助这个类来验证 bindingKey 是否合法
 */
public class Router {
    // bindingKey 的构造规则:
    // 1. 数字, 字母, 下划线
    // 2. 使用 . 分割成若干部分
    // 3. 允许存在 * 和 # 作为通配符, 但是通配符只能作为独立的阶段.
    public boolean checkBindingKey(String bindingkey) {
        if (bindingkey.length() == 0) {
            // 空字符串, 也是合法情况, 例如在 使用 direct / fanout 交换机的时候, bindingKey 是用不上的.
            return true;
        }
        // 检查字符串中不能存在非法字符
        for (int i = 0; i < bindingkey.length(); i++) {
            char ch = bindingkey.charAt(i);
            // 判定该字符是否是大写字母
            if (ch >= 'A' && ch <= 'Z') {
                continue;
            }
            // 判定该字母是否是小写字母
            if (ch >= 'a' && ch <= 'z') {
                continue;
            }
            // 判定字母是否是阿拉伯字母
            if (ch >='0' && ch <= '9') {
                continue;
            }
            if (ch == '_' || ch == '.' || ch == '#' || ch == '*') {
                continue;
            }
            return false;
        }
        // 检查 * 或者 # 是否是独立的部分
        // aaa.*.bbb 为合法情况. aaa.b*.ccc 这是非法情况
        String[] words = bindingkey.split("\\."); // 重点注意这种写法.
        for (String word : words) {
            // 检查 word 长度 > 1 并且包含了 * 或者 #, 就是 非法的格式了.
            if (word.length() > 1 && (word.contains("*") || word.contains("#"))) {
                return false;
            }
        }
        // 约定一下, 通配符之间的相邻关系.
        // 1. aaa.#.#.bbb => 非法
        // 2. aaa.*.#.bbb => 非法
        // 3. aaa.#.*.bbb => 非法
        // 4. aaa.*.*.bbb => 合法
        // 以上前三种  因为匹配的逻辑繁琐,因此判定直接 false.
        for (int i = 0; i < words.length - 1; i++) {
            // 判断连续两个 ##
            if (words[i].equals("#") && words[i + 1].equals("#")) {
                return false;
            }
            // 判断 * 连着 #
            if (words[i].equals("*") && words[i + 1].equals("#")) {
                return false;
            }
            // 判断 # 连着 *
            if (words[i].equals("#") && words[i + 1].equals("*")) {
                return false;
            }
            return true;
        }
        return true;
    }

    // routingKey 的构造规则:
    // 1. 数字,字母, 下划线
    // 2. 使用 . 来分割为若干个部分.
    public boolean checkRoutingKey (String routingKey) {
        if (routingKey.length() == 0) {
            // 空字符串, 合法的情况. 例如在使用 fanout 交换机的时候, routingkey 用不上, 就可以设为 ""
            return true;
        }
        for (int i = 0; i < routingKey.length(); i++) {
            char ch = routingKey.charAt(i);
            // 判定该字符是否是大写字母
            if (ch >= 'A' && ch <= 'Z') {
                continue;
            }
            // 判定该字母是否是小写字母
            if (ch >= 'a' && ch <= 'z') {
                continue;
            }
            // 判定字母是否是阿拉伯字母
            if (ch >='0' && ch <= '9') {
                continue;
            }
            if (ch == '_' || ch == '.') {
                continue;
            }
            return false;
        }
        return true;
    }

    // 该方法用来判定该消息是否可以转发给这个绑定对应的队列.
    public boolean route(ExchangeType exchangeType, Binding binding, Message message) throws MqException {
        // 根据不同的 exchangeType 使用不同的判定转发规则.
        if (exchangeType == ExchangeType.FANOUT) {
            // 如果是 FANOUT 类型, 则该交换机上绑定的所有队列都需要转发.
            return true;
        } else if (exchangeType == ExchangeType.TOPIC) {
            // 如果是 TOPIC 主体交换机.
            return routeTopic(binding,message);
        } else {
            // 其他情况是不应该存在的
            throw new MqException("[Router] 交换机类型非法! exchangeType=" + exchangeType);
        }
    }

    private boolean routeTopic(Binding binding, Message message) {
        // 先将 key 进行切分
        String[] bindingTokens = binding.getBindingKey().split("\\.");
        String[] routingTokens = message.getRoutingKey().split("\\.");

        // 引入两个下标, 指向上述两个数组, 初始情况下都是为 0
        int bindingIndex = 0;
        int routingIndex = 0;
        while ((bindingIndex < bindingTokens.length) && (routingIndex < routingTokens.length)) {
            if (bindingTokens[bindingIndex].equals("*")) {
                // 如果遇到 *, 直接进入下一轮. *可以匹配任意一个部分 .
                bindingIndex++;
                routingIndex++;
            } else if (bindingTokens[bindingIndex].equals("#")) {
                // 如果遇到 #, 需要先看看有没有下一个位置.
                bindingIndex++;
                if (bindingIndex == bindingTokens.length) {
                    // 该 # 之后没有东西了, 说明此时一定能匹配成功了!
                    return true;
                }
                // 代码到这, 说明 # 后面还有东西, 拿着这个内容, 去 routingKey 中往后找, 找到相应的位置.
                // findNextMatch 这个方法用来查找该部分在 routingKey 中的位置. 返回其下标. 如果不存在则返回 -1.
                routingIndex = findNextMatch(routingTokens,routingIndex,bindingTokens[bindingIndex]);
                if (routingIndex == -1) {
                    // 没找到匹配的结果. 匹配失败
                    return false;
                }
                // 找到匹配的情况, 继续往后匹配.
                bindingIndex++;
                routingIndex++;
            } else {
                // 如果遇到普通字符串.
                if (!bindingTokens[bindingIndex].equals(routingTokens[routingIndex])) {
                    return false;
                }
                bindingIndex++;
                routingIndex++;
            }
        }
        // 判定是否双方同时到达末尾
        if ((bindingIndex == bindingTokens.length) && (routingIndex == routingTokens.length)) {
            return true;
        }
        return false;
    }

    private int findNextMatch(String[] routingTokens, int routingIndex, String bindingToken) {
        for (int i = routingIndex; i < routingTokens.length; i++) {
            if (routingTokens[i].equals(bindingToken)) {
                return i;
            }
        }
        return -1;
    }
}
